#pragma once

#include <iostream>
#include <string>
#include <set>
#include <cstring>
#include <cstdio>

#include "Log.hpp"
#include "InetAddr.hpp"

class Command
{
public:
    Command()
    {
        // 这里可以写一个配置文件，配置文件中是能够执行的命令字符串，也就是安全的命令
        // 读取配置文件，将配置文件中的命令字符串保存到内存中，该配置文件称为白名单
        // 或者写一个黑名单，把不安全的命令存储到内存中，在安全命令检查时，不在黑名单中的命令则是安全命令
        _safe_command.insert("ls");
        _safe_command.insert("touch");
        _safe_command.insert("whoami");
        _safe_command.insert("pwd");
        _safe_command.insert("which");
    }
    // ls;rm -rf /这条命令是无法检查出来的，程序的一个漏洞
    bool CheckSafe(const std::string &cmd_str)
    {
        for (auto &tmp : _safe_command)
        {
            if (strncmp(tmp.c_str(), cmd_str.c_str(), tmp.size()) == 0) // 只比较前几个字符是否相等
            {
                return true;
            }
        }
        return false;
    }
    std::string ExecuteCommand(std::string cmd_str)
    {
        // 先检查该命令是否是安全的
        if (!CheckSafe(cmd_str))
            return "command unsafe";
        std::string result;
        FILE *f = popen(cmd_str.c_str(), "r");
        if (f)
        {
            char line[1024];
            // 按行读取fp管道文件
            while (fgets(line, sizeof(line), f)) // C风格的读取size不用-1，有'\0'字符
            {
                result += line;
            }
            return result;
        }
        else
        {
            return "execute command error";
        }
        // 关闭popen()中打开的管道文件
        pclose(f);
    }
    void Service(int sockfd, InetAddr addr)
    {
        while (true)
        {
            char command_buff[1024];
            ssize_t n = recv(sockfd, command_buff, sizeof(command_buff) - 1, 0);
            if (n > 0)
            {
                command_buff[n] = 0;
                LOG(INFO, "get command from client %s, command: %s\n", addr.AddrStr().c_str(), command_buff);

                std::string result = ExecuteCommand(command_buff);

                send(sockfd, result.c_str(), result.size(), 0);
            }
            else if (n == 0)
            {
                LOG(INFO, "client %s quit\n", addr.AddrStr().c_str());
                break;
            }
            else
            {
                LOG(ERROR, "read error: %s\n", addr.AddrStr().c_str());
                break;
            }
        }
    }
    ~Command()
    {
    }

private:
    std::set<std::string> _safe_command;
};